/*
 *  pixelScreen.h
 *  
 *
 *  Created by Jason Hsu on 2/8/11.
 * 
 */

#include "pixels.h"
#include "stdlib.h"
#include "math.h"
#include "tuple3.h"
#include <set>
#include "texture.h"
#include "dirent.h"
#include "sys/types.h"
#include "errno.h"
#include <string>
#include "host.h"

const string IMAGES_DIRECTORY = "./images/";

class PixelScreen
{
private:
	GLint myViewport[4];					//information about the viewport
	GLint myPixelsPerRow;					//number of pixels per row
	GLdouble myDefaultSideLength;			//default side length of pixels for this screen size
	GLint myNumInitialPixels;						//number of pixels on the screen to start with
	set<Texture*> myTextureSet;				//set of all pictures (textures) that can be displayed
	set<Texture*>::iterator myTextureIter;	//iterator pointing to current picture being displayed
	Pixels* myPixels;						//pointer to beginning of array containing pixels
	set<Pixels*> myAnimateSet;				//set of pixels that are currently undergoing animation
	GLint myNumSplits;						//tracks number of times pixels split (track when picture complete)
	GLint myMaxNumSplits;					//number of splits before picture is complete (5 levels)...might be wrong
	GLint myMaxLevels;						//max levels of divisions (num times pixels can split)
	getHostID myHostID;						//which render node is running this instance of the app
	
public:
	/*
	 * Default constructor. Gathers textures and initializes screen with default pixels.
	 */
	PixelScreen()
	{
		glGetIntegerv(GL_VIEWPORT, myViewport);
		myDefaultSideLength = gcf(myViewport[2], myViewport[3]);
		myPixelsPerRow = myViewport[2]/myDefaultSideLength;
		myNumInitialPixels = myViewport[2]*myViewport[3]/(myDefaultSideLength*myDefaultSideLength);
		getTextureFiles(IMAGES_DIRECTORY);	//gets texture files from directory and adds to myTextureSet
		myTextureIter = myTextureSet.begin();
		myHostID = getHostID();
		
		initPixelScreen(*myTextureIter);
	}
	
	/*
	 * Initializes the screen to its default state using current texture. For the Link Media Wall
	 * aspect ratio of 1:4.8, the pixelScreen will init as 5x24.
	 *
	 * Texture* texture is the current texture that is going to be drawn on screen.
	 */
	void initPixelScreen(Texture* texture)
	{
		GLint row_counter = 0;
		myPixels = new Pixels[myNumInitialPixels];
		for(GLint k = 0; k<myNumInitialPixels; k++)
		{
			myPixels[k].setSideLength(myDefaultSideLength);
			GLint x = myDefaultSideLength*k - myViewport[2]*row_counter;
			GLint y = -myDefaultSideLength*row_counter;
			myPixels[k].setPosition(x, y, 0.0);
			myPixels[k].setColor(texture);
			if(x + myDefaultSideLength >= myViewport[2])
				row_counter++;
		}
		myNumSplits = 0;
		myMaxLevels = floor(log(myDefaultSideLength)/log(2));
		for(GLint m = 0; m<myMaxLevels; m++)
		{
			myMaxNumSplits += myNumInitialPixels*pow(4,m);
		}
	}
	
	/*
	 * Draws the pixels on the screen. Also performs culling depending on which render node the
	 * app is running on.
	 */
	void draw()
	{
		GLint hostID = myHostID.hostID;
		//GLint hostID = 0;
		if(hostID < -1 || hostID > 11)
		{
			return;
		}
		else if(hostID == -1)
		{
			for(GLint k = 0; k<myNumInitialPixels; k++)
			{
				myPixels[k].draw();
			}
		}
		else 
		{
			GLint nodeWidth = myPixelsPerRow/3;
			for(GLint k = 0; k<nodeWidth; k++)
			{
				GLint rowCount = floor(hostID/3);
				GLint startIndex = hostID%3;
				GLint index = rowCount*myPixelsPerRow + startIndex*nodeWidth;
				for(GLint m = 0; m<nodeWidth; m++)
				{
					myPixels[index].draw();
					index++;
				}
			}
		}
	}
	
	/*
	 * Places the pixels that the mouse pointer touches into the animation set.
	 *
	 * GLint x is x coordinate of mouse on screen
	 * GLint y is y coordinate of mouse on screen
	 */
	void trackMouse(GLint x, GLint y)
	{
		if(x < myViewport[2] && y < myViewport[3] && x > 0 && y > 0)
		{
			GLint index = round(x/(GLint)myDefaultSideLength + y/(GLint)myDefaultSideLength*myPixelsPerRow);
			myAnimateSet.insert(myPixels[index].getPixelFromScreenCoordinates(x, y));
		}
	}
	
	/*
	 * Places the pixels that webcam blobs touch into the animation set.
	 *
	 * set<Points*>* points is set of points on screen where blob is detected
	 */
	void trackMovement(set<Point3*>* points)
	{
		set<Point3*>::iterator iter;
		for(iter = points->begin(); iter != points->end(); iter++)
		{
			GLint x = (*iter)->x;
			GLint y = (*iter)->y;
			GLint index = round(x/(GLint)myDefaultSideLength + y/(GLint)myDefaultSideLength*myPixelsPerRow);
			myAnimateSet.insert(myPixels[index].getPixelFromScreenCoordinates(x, y));
			delete *iter;	//deletes new points created in imageprocessor.h
		}
	}
	
	/*
	 * Updates the PixelScreen. 
	 * 1.	Each pixel in the animation set gets updated (fades to white); if the pixel
	 *		is completely white, it splits, and the pixel is removed from the animation
	 *		set.
	 * 2.	If the texture is complete (all pixels completely split), re-initialize
	 *		PixelScreen with new texture.
	 */
	void update()
	{
		set<Pixels*>::iterator iter;
		
		/* ugh...took me forever to figure out that erasing iterators from sets while iterating over the set
		 * can cause undefined behavior. that's why this for loop is being incremented oddly. The iter++
		 * statement has to be placed inside the erase method so that it passes the current iter to the method
		 * (to be erased) but also increments the iterator (before it passes it). this prevents the aforementioned
		 * undefined behavior (erased iterators get deallocated, and placing iter++ in the for loop would have
		 * resulted in iter++ being called on a deallocated iterator; thus, undefined behavior...i think). was
		 * causing segfaults.
		 */
		for(iter = myAnimateSet.begin(); iter != myAnimateSet.end(); )
		{
			Pixels* thisPixel = *iter;
			GLint pixelFaded = thisPixel->fadeToWhite(myMaxLevels); //true when completely white, false if not yet, 2 if turned off
			if(pixelFaded == 2)	//this pixel is off; nothing should happen, and it should be removed from animation set
			{
				myAnimateSet.erase(iter++);	//see above note on why iter++ is here
			}
			else if(pixelFaded)	//pixel is completely white; split, set new colors, and remove from animation set
			{
				thisPixel->split(myMaxLevels);
				myNumSplits++;
				thisPixel->setColor(*myTextureIter);
				myAnimateSet.erase(iter++);	//see above note on why iter++ is here
			}
			else {
				++iter;	//see above note on why ++iter is here
			}

		}
		if(myNumSplits >= myMaxNumSplits)
		{
			changeTexture();
		}
	}
	
	/*
	 * Changes current texture being drawn on PixelScreen and then re-initializes screen.
	 */
	void changeTexture()
	{
		myTextureIter++;
		if(myTextureIter == myTextureSet.end())	//if iterator pointing to last element, go back to beginning
		{
			myTextureIter = myTextureSet.begin();
		}
		
		for(GLint k = 0; k<myNumInitialPixels; k++)
		{
			delete &myPixels[k];	//deletes pixels created in previous screen
		}
		myAnimateSet.clear();	//don't need to delete things in here because they are the pixels deleted above
		initPixelScreen(*myTextureIter);
	}
	
	/*
	 * Opens up a directory stream and reads in the file names from the given directory.
	 * Takes all the jpeg files, turns them into textures, and places them into the 
	 * texture set. Got this from the internet.
	 * 
	 * string directoryName is the name of the directory in which the texture files are located
	 */
	void getTextureFiles(string directoryName)
	{
		DIR* directory;
		if((directory = opendir(directoryName.c_str())) == NULL)
		{
			cout << "Error(" << errno << ") opening " << directoryName << endl;
		}
		struct dirent* directoryEntry;
		while((directoryEntry = readdir(directory)) != NULL)
		{
			string dirString(IMAGES_DIRECTORY);
			dirString += string(directoryEntry->d_name);
			if(dirString.find(".jpg") != string::npos)
			{
				myTextureSet.insert(new Texture(dirString));
			}
		}
		closedir(directory);
	}
	
	/*
	 * Finds the greatest common factor between two integers.
	 */
	int gcf(int a, int b)
	{
		int c = a % b;
		while(c != 0)
		{
			a = b;
			b = c;
			c = a % b;
		}
		return b;
	}
};